/*
 * DDR driver for Synopsys DWC DDR PHY.
 * Used by Jz4775, JZ4780...
 *
 * Copyright (C) 2013 Ingenic Semiconductor Co.,Ltd
 * Author: Zoro <ykli@ingenic.cn>
 *
 * SPDX-License-Identifier:	GPL-2.0+
 */

#include <config.h>
#include <common.h>
#include <asm/io.h>
#include <asm/arch/ddr_reg_values.h>
#include <asm/arch/jzsoc.h>
#include <ddr/ddr_chips.h>
#include <ddr/ddr_params.h>
#include <ddr/ddrc.h>

static unsigned int out_imp_table[] = DDRP_IMPANDCE_ARRAY;
static unsigned int odt_imp_table[] = DDRP_ODT_IMPANDCE_ARRAY;
static unsigned char rzq_table[] = DDRP_RZQ_TABLE;

void ddr_controller_init(int bypass,enum ddr_type type)
{
	void __iomem *ddrc_regs = (void __iomem *)DDRC_BASE;

	writel(0, ddrc_regs + DDRC_CTRL);
	/* DDRC CFG init*/
	writel(DDRC_CFG_VALUE, ddrc_regs + DDRC_CFG);
	/* DDRC timing init*/
	writel(DDRC_TIMING1_VALUE, ddrc_regs + DDRC_TIMING(1));
	writel(DDRC_TIMING2_VALUE, ddrc_regs + DDRC_TIMING(2));
	writel(DDRC_TIMING3_VALUE, ddrc_regs + DDRC_TIMING(3));
	writel(DDRC_TIMING4_VALUE, ddrc_regs + DDRC_TIMING(4));
	writel(DDRC_TIMING5_VALUE, ddrc_regs + DDRC_TIMING(5));
	writel(DDRC_TIMING6_VALUE, ddrc_regs + DDRC_TIMING(6));

	/* DDRC memory map configure*/
	writel(DDRC_MMAP0_VALUE, ddrc_regs + DDRC_MMAP0);
	writel(DDRC_MMAP1_VALUE, ddrc_regs + DDRC_MMAP1);
	writel(DDRC_CTRL_CKE | DDRC_CTRL_ALH, ddrc_regs + DDRC_CTRL);
	writel(DDRC_REFCNT_VALUE, ddrc_regs + DDRC_REFCNT);
	writel(DDRC_CTRL_VALUE, ddrc_regs + DDRC_CTRL);

	writel(0x03100f0e, ddrc_regs + DDRC_REMAP(1));
	writel(0x07060504, ddrc_regs + DDRC_REMAP(2));
	writel(0x0b0a0908, ddrc_regs + DDRC_REMAP(3));
	writel(0x01000d0c, ddrc_regs + DDRC_REMAP(4));
	writel(0x13121102, ddrc_regs + DDRC_REMAP(5));

	writel(readl(DDRC_STATUS) & ~DDRC_STATUS_MISS, ddrc_regs + DDRC_STATUS);

	if(DDRC_AUTOSR_EN_VALUE){
		if(!bypass)
			writel(0, ddrc_regs + DDRC_DLP);
		else
			writel((9 << 28) | 0xf, ddrc_regs + DDRC_CLKSTP_CFG);
	}
	writel(DDRC_AUTOSR_EN_VALUE, ddrc_regs + DDRC_AUTOSR_EN);
}

static void wait_ddrp_pgsr(unsigned int wait_val,int timeout)
{
	void __iomem *ddrc_regs = (void __iomem *)DDRC_BASE;

	while(((readl(ddrc_regs + DDRP_PGSR) & wait_val) != wait_val) && --timeout);
}

static int ddr_training_hardware(int bypass)
{
	void __iomem *ddrc_regs = (void __iomem *)DDRC_BASE;
	int result = 0;
	int timeout = 500000;
	unsigned int wait_val = 0;
	unsigned int pir_val = DDRP_PIR_INIT | DDRP_PIR_QSTRN;

	wait_val = DDRP_PGSR_IDONE | DDRP_PGSR_DTDONE;
	if(bypass) {
		pir_val |= DDRP_PIR_DLLBYP | DDRP_PIR_LOCKBYP;
	}
	writel(pir_val, ddrc_regs + DDRP_PIR);
	wait_ddrp_pgsr(wait_val,timeout);
	result = readl(ddrc_regs + DDRP_PGSR);
	if (!(result & (DDRP_PGSR_DTERR | DDRP_PGSR_DTIERR)))
		result = 0;

	return result;
}

static void ddr_phy_param_config(int bypass,enum ddr_type type)
{
	void __iomem *ddrc_regs = (void __iomem *)DDRC_BASE;

	writel(DDRP_DCR_VALUE, ddrc_regs + DDRP_DCR);
	writel(DDRP_PTR0_VALUE, ddrc_regs + DDRP_PTR0);
	writel(DDRP_PTR1_VALUE, ddrc_regs + DDRP_PTR1);
	writel(DDRP_PTR2_VALUE, ddrc_regs + DDRP_PTR2);
	writel(DDRP_DTPR0_VALUE, ddrc_regs + DDRP_DTPR0);
	writel(DDRP_DTPR1_VALUE, ddrc_regs + DDRP_DTPR1);
	writel(DDRP_DTPR2_VALUE, ddrc_regs + DDRP_DTPR2);

	writel(DDRP_DX0GCR_VALUE, ddrc_regs + DDRP_DXGCR(0));
	writel(DDRP_DX1GCR_VALUE, ddrc_regs + DDRP_DXGCR(1));
	writel(DDRP_DX2GCR_VALUE, ddrc_regs + DDRP_DXGCR(2));
	writel(DDRP_DX3GCR_VALUE, ddrc_regs + DDRP_DXGCR(3));

	writel(DDRP_PGCR_VALUE, ddrc_regs + DDRP_PGCR);
	/***************************************************************
	 *  DXCCR:
	 *       DQSRES:  4...7bit  is DQSRES[].
	 *       DQSNRES: 8...11bit is DQSRES[] too.
	 *
	 *      Selects the on-die pull-down/pull-up resistor for DQS pins.
	 *      DQSRES[3]: selects pull-down (when set to 0) or pull-up (when set to 1).
	 *      DQSRES[2:0] selects the resistor value as follows:
	 *      000 = Open: On-die resistor disconnected
	 *      001 = 688 ohms
	 *      010 = 611 ohms
	 *      011 = 550 ohms
	 *      100 = 500 ohms
	 *      101 = 458 ohms
	 *      110 = 393 ohms
	 *      111 = 344 ohms
	 *****************************************************************
	 *      Note: DQS resistor must be connected for LPDDR/LPDDR2    *
	 *****************************************************************
	 *     the config will affect power and stablity
	 *
	 *     DXIOM: 1bit
	 *           Data I/O Mode: Selects SSTL mode (when set to 0)
	 *                           or CMOS mode (when set to 1)
	 *     I/O Mode: I/O Mode select
	 *           0 = DDR2/DDR3/DDR3L/LPDDR2 mode
	 *           1 = Mobile DDR mode
	 *
	 */
	writel(DDRP_MR0_VALUE, ddrc_regs + DDRP_MR0);
	writel(DDRP_MR1_VALUE, ddrc_regs + DDRP_MR1);
	writel(DDRP_MR2_VALUE, ddrc_regs + DDRP_MR2);
}

static void ddr_phy_impedance_calibration(unsigned int cal_value)
{
	void __iomem *ddrc_regs = (void __iomem *)DDRC_BASE;
	unsigned int pull[4];
	unsigned int *out_imp = out_imp_table;
	unsigned int *odt_imp = odt_imp_table;
	unsigned char *rzq = rzq_table;
	unsigned int val = cal_value;
	int i,j;

	for(i = 0;i < 4;i++){
		pull[i] = (val >> (5 * i)) & 0x1f;
		for(j = 0;j < ARRAY_SIZE(rzq_table);j++)
			if(pull[i] == rzq[j])
				break;
		pull[i] = j;

	}

	pull[0] = (out_imp[0] * pull[0] + out_imp[1] / 2) / out_imp[1];
	pull[1] = (out_imp[0] * pull[1] + out_imp[1] / 2) / out_imp[1];

	pull[2] = (odt_imp[0] * pull[2] + odt_imp[1] / 2) / odt_imp[1];
	pull[3] = (odt_imp[0] * pull[3] + odt_imp[1] / 2) / odt_imp[1];

	val = readl(ddrc_regs + DDRP_ZQXCR0(0));
	val &= 0x10000000;

	for(i = 0;i < 4;i++){
		val |= (rzq[pull[i]] << (5 * i));
	}

	val |= DDRP_ZQXCR_ZDEN;
	writel(val, ddrc_regs + DDRP_ZQXCR0(0));
}
static void ddr_phy_init_dram(int bypass,enum ddr_type type)
{
	void __iomem *ddrc_regs = (void __iomem *)DDRC_BASE;
	int timeout = 10000;
	unsigned int pir_val;
	unsigned int val;
	unsigned int wait_val = 0;

	pir_val = DDRP_PIR_INIT | DDRP_PIR_DLLSRST | DDRP_PIR_ITMSRST | DDRP_PIR_DRAMINT | DDRP_PIR_DLLLOCK | DDRP_PIR_ZCAL;
	wait_val = DDRP_PGSR_IDONE | DDRP_PGSR_ZCDONE | DDRP_PGSR_DIDONE | DDRP_PGSR_DLDONE;

	pir_val |= DDRP_PIR_DRAMRST;

	/* for ddr impedance calibration. */
	val = readl(ddrc_regs + DDRP_ZQXCR0(0));
	val &= ~((1 << 31) | (1 << 29) | (1 <<28) | 0xfffff);
	val |= (1 << 30);
	writel(val, ddrc_regs + DDRP_ZQXCR0(0));
	writel(DDRP_ZQNCR1_VALUE, ddrc_regs + DDRP_ZQXCR1(0));

	if(bypass) {
		pir_val &= ~DDRP_PIR_DLLLOCK;
		pir_val &= ~DDRP_PIR_DLLSRST;
		pir_val |= DDRP_PIR_DLLBYP | DDRP_PIR_LOCKBYP;
		// DLL Disable: only bypassmode
		writel(0x1 << 31, ddrc_regs + DDRP_ACDLLCR);
		// 200M bypass.
		val = readl(ddrc_regs + DDRP_DLLGCR);
		val |= 1 << 23;
		writel(val, ddrc_regs + DDRP_DLLGCR);

		/*  LPDLLPD:  only for ddr bypass mode
		 * Low Power DLL Power Down: Specifies if set that the PHY should respond to the *
		 * DFI low power opportunity request and power down the DLL of the PHY if the *
		 * wakeup time request satisfies the DLL lock time */
		val = readl(ddrc_regs + DDRP_DSGCR);
	 	val &= ~(1 << 4);
		writel(val, ddrc_regs + DDRP_DSGCR);
		wait_val &= ~DDRP_PGSR_DLDONE;
	}

	wait_ddrp_pgsr((DDRP_PGSR_IDONE | DDRP_PGSR_DLDONE | DDRP_PGSR_ZCDONE),0x10000);
	writel(pir_val, ddrc_regs + DDRP_PIR);
	wait_ddrp_pgsr(wait_val,timeout);

	val = readl(ddrc_regs + DDRP_ZQXSR0(0));
	if(!(val & 0x40000000))
		ddr_phy_impedance_calibration(val);
}
static struct jzsoc_ddr_hook *ddr_hook = NULL;
void register_ddr_hook(struct jzsoc_ddr_hook * hook)
{
	ddr_hook = hook;
}
static void force_no_slefresh(void)
{
	void __iomem *ddrc_regs = (void __iomem *)DDRC_BASE;

	/* force CKE1 CS1 HIGH */
	writel(DDRC_CFG_VALUE | DDRC_CFG_CS1EN | DDRC_CFG_CS0EN, ddrc_regs + DDRC_CFG);
	writel((1 << 1), ddrc_regs + DDRC_CTRL);
}
void ddr_phy_init(int bypass,enum ddr_type type)
{
	void __iomem *ddrc_regs = (void __iomem *)DDRC_BASE;

	writel(0x150000, ddrc_regs + DDRP_DTAR);
	/* DDR training address set*/
	ddr_phy_param_config(bypass,type);
	ddr_phy_init_dram(bypass,type);

	force_no_slefresh();
	ddr_training_hardware(bypass);
}

void sdram_init(void)
{
	void __iomem *ddrc_regs = (void __iomem *)DDRC_BASE;
	void __iomem *cpm_regs = (void __iomem *)CPM_BASE;
	enum ddr_type type;
	unsigned int bypass = 0;
	uint32_t ddr_cdr;

	type = DDR3;

	/* setup DDR clock */
	ddr_cdr = CONFIG_SYS_MPLL_FREQ / CONFIG_SYS_MEM_FREQ - 1;
	writel(ddr_cdr | CPM_DDRCDR_DCS_MPLL | CPM_DDRCDR_CE, cpm_regs + CPM_DDRCDR);
	while (readl(cpm_regs + CPM_DDRCDR) & CPM_DDRCDR_DDR_BUSY);

	if(CONFIG_SYS_MEM_FREQ < 300000000)
		bypass = 1;

	/*
	 * WARNING: 2015-01-08
	 * DDR CLK GATE(CPM_DRCG 0xB00000D0), BIT6 must set to 1 (or 0x40).
	 * If clear BIT6, chip memory will not stable, gpu hang occur.
	 */
	/* auto DDR clk gating */
	writel(0x3 | (1 << 6), cpm_regs + CPM_DRCG);
	mdelay(1);
	writel(0x11 | (1 << 6), cpm_regs + CPM_DRCG);
	mdelay(1);

	/* reset DDR crtl,cfg, dll and dfi */
	writel(0xf << 20, ddrc_regs + DDRC_CTRL);
	mdelay(1);
	writel(0, ddrc_regs + DDRC_CTRL);
	mdelay(1);

	/* DDR PHY init*/
	ddr_phy_init(bypass,type);

	/* DDR Controller init*/
	ddr_controller_init(bypass,type);

	if(bypass)
		writel(readl(cpm_regs + CPM_DDRCDR) | 1 << 26, cpm_regs + CPM_DDRCDR);
}

DECLARE_GLOBAL_DATA_PTR;

int dram_init(void)
{
	void __iomem *ddrc_regs = (void __iomem *)DDRC_BASE;

	/* SDRAM size was calculated when compiling. */
#ifndef EMC_LOW_SDRAM_SPACE_SIZE
#define EMC_LOW_SDRAM_SPACE_SIZE 0x10000000 /* 256M */
#endif /* EMC_LOW_SDRAM_SPACE_SIZE */

	unsigned int ram_size;
	ddrc_cfg_t ddrc_cfg = {.d32 = readl(ddrc_regs + DDRC_CFG)};
	uint16_t col0 = ddrc_cfg.b.COL0 + 8, row0 = ddrc_cfg.b.ROW0 + 12;
	uint8_t dw = ddrc_cfg.b.DW ? 4 : 2, ba0 = ddrc_cfg.b.BA0 ? 8 : 4;
	uint32_t chip_0_szie = (uint32_t)(1 << (col0 + row0)) * dw * ba0;
	ram_size = chip_0_szie + (unsigned int)(DDR_CHIP_1_SIZE);
	if (ram_size > EMC_LOW_SDRAM_SPACE_SIZE)
		ram_size = EMC_LOW_SDRAM_SPACE_SIZE;

	gd->ram_size = ram_size;

	return 0;
}
